#include "FtpDownloader.h"
#include "Util/onceToken.h"
#include "Util/logger.h"

using namespace ZL::Util;

FtpDownloader::FtpDownloader(const string &user_name, const string &passwd) {
    //int curl
    static onceToken s_token([]() {
        curl_global_init(CURL_GLOBAL_DEFAULT);
    }, []() {
        curl_global_cleanup();
    });

    _user_name = user_name;
    _passwd = passwd;
    _curl = curl_easy_init();
}

FtpDownloader::~FtpDownloader() {
    cancel();
    /* always cleanup */
    curl_easy_cleanup(_curl);
}

size_t curl_onDownload(void *buffer, size_t size, size_t nmemb, void *stream) {
    FtpDownloader *_this = (FtpDownloader *) stream;
    return _this->onProgressInner((char *) buffer, size * nmemb);
}

size_t curl_onHeader(void *buffer, size_t size, size_t nmemb, void *stream) {
    FtpDownloader *_this = (FtpDownloader *) stream;
    if (_this->canceled()) {
        return -1;
    }
    //DebugL << (char *)buffer;
    return size * nmemb;
}

int curl_onProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
                    curl_off_t ulnow) {
    FtpDownloader *_this = (FtpDownloader *) clientp;
    DebugL << dltotal << " " << dlnow << " " << ultotal << " " << ulnow;
    return 0;
}


bool
FtpDownloader::start(const string &url, int offset, int connectTimeoutSec, int completeTimeoutSec) {
    if (!cancel()) {
        WarnL << "不能在本线程重启下载！";
        return false;
    }
    lock_guard<decltype(_mtx)> lck(_mtx);
    _thread.reset(new thread(&FtpDownloader::startDownload, this, url, offset, connectTimeoutSec,
                             completeTimeoutSec));
    return true;
}

bool FtpDownloader::cancel() {
    _cancel = true;
    lock_guard<decltype(_mtx)> lck(_mtx);
    if (_thread) {
        if (this_thread::get_id() != _thread->get_id()) {
            //线程自己不能销毁自己
            int i = 10;
            while (--i) {
                try {
                    _thread->join();
                    InfoL << "join success:" << _thread;
                    break;
                } catch (std::exception &ex) {
                    WarnL << "join failed:" << _thread << " " << i << ex.what() << endl;
                    usleep(100 * 1000);
                }
            }
            _thread = nullptr;
        }
    }
    return _thread ? false : true;
}


bool FtpDownloader::pause(bool flag) {
    auto code = curl_easy_pause(_curl, flag ? CURLPAUSE_ALL : CURLPAUSE_CONT);
    if (code != CURLE_OK) {
        WarnL << curl_easy_strerror(code);
    } else {
        InfoL << flag;
    }
    return code == CURLE_OK;
}

int FtpDownloader::onProgressInner(char *data, int size) {
    if (_total == 0) {
        curl_easy_getinfo(_curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &_total);
        curl_easy_getinfo(_curl, CURLINFO_FILETIME, &_fileTime);
        //DebugL << _fileTime;
        _total += _offset;
        onStart(_total, _offset, _fileTime);
    }
    onProgress(data, size);
    return _cancel ? -1 : size;
}


bool FtpDownloader::canceled() const {
    return _cancel;
}

void FtpDownloader::startDownload(const string &url, int offset, int connectTimeoutSec,
                                  int completeTimeoutSec) {
    curl_easy_reset(_curl);
    static CURLSH *share_handle = curl_share_init();
    static onceToken s_token([]() {
        //设置全局dns解析
        curl_share_setopt(share_handle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
    }, nullptr);
    //设置全局dns解析
    curl_easy_setopt(_curl, CURLOPT_SHARE, share_handle);
    //全局dns过期时间
    curl_easy_setopt(_curl, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5L);
    //设置下载地址
    curl_easy_setopt(_curl, CURLOPT_URL, url.data());
    //设置下载回调函数
    curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, curl_onDownload);
    //设置下载回调用户数据指针
    curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this);
    //设置不触发信号量
    curl_easy_setopt(_curl, CURLOPT_NOSIGNAL, 1L);
    /* Switch on full protocol/debug output */
    //curl_easy_setopt(_curl, CURLOPT_VERBOSE, 1L);

    //设置低速自动断开连接
    curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_TIME, _minSpeedSec * 1L);
    curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_LIMIT, _minSpeed * 1L);

    //curl_easy_setopt(_curl, CURLOPT_XFERINFOFUNCTION, curl_onProgress);
    //curl_easy_setopt(_curl, CURLOPT_XFERINFODATA, this);
    //curl_easy_setopt(_curl, CURLOPT_NOPROGRESS, 0L);
    curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, curl_onHeader);
    curl_easy_setopt(_curl, CURLOPT_HEADERDATA, this);
    //获取文件时间
    curl_easy_setopt(_curl, CURLOPT_FILETIME, 1L);

    /*设置用户名*/
    if (!_user_name.empty()) {
        curl_easy_setopt(_curl, CURLOPT_USERNAME, _user_name.data());
    }
    /*设置密码*/
    if (!_passwd.empty()) {
        curl_easy_setopt(_curl, CURLOPT_PASSWORD, _passwd.data());
    }

    //设置恢复下载offset
    if (offset) {
        curl_easy_setopt(_curl, CURLOPT_RESUME_FROM, offset * 1L);
    }

    //连接超时时间
    if (connectTimeoutSec) {
        curl_easy_setopt(_curl, CURLOPT_CONNECTTIMEOUT, connectTimeoutSec * 1L);
    }

    //下载完成超时时间
    if (completeTimeoutSec) {
        curl_easy_setopt(_curl, CURLOPT_TIMEOUT, completeTimeoutSec * 1L);
    }

    _offset = offset;
    _total = 0;
    _cancel = false;
    CURLcode code = curl_easy_perform(_curl);

    switch (code) {
        case CURLE_OK:
            onComplete();
            break;
        case CURLE_WRITE_ERROR:
            onCancel();
            break;
        case CURLE_BAD_DOWNLOAD_RESUME: {
            double length = 0;
            if (CURLE_OK == curl_easy_getinfo(_curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length)) {
                if (length <= offset) {
                    //文件下载完毕
                    onComplete();
                    break;
                }
            }
        }
        default:
            onFailed(code, curl_easy_strerror(code));
            break;
    }
}

int FtpDownloader::downloadSpeed() {
    double speed = 0;
    curl_easy_getinfo(_curl, CURLINFO_SPEED_DOWNLOAD, &speed);
    return speed;
}

void FtpDownloader::setLowSpeedLimit(int minSpeed, int minSpeedSec) {
    _minSpeed = minSpeed;
    _minSpeedSec = minSpeedSec;
}

void FtpDownloader::wait() {
    while (true) {
        decltype(_thread) threadCopy;
        {
            lock_guard<decltype(_mtx)> lck(_mtx);
            threadCopy = _thread;
        }
        if (threadCopy) {
            try {
                threadCopy->join();
            } catch (...) {
                break;
            }
        } else {
            break;
        }
    }
}





